package com.main.mainpro.system.common;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Stack;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author Administrator
 */
@Component
public class SpringBeanUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    private static String packageuri = "\\src\\main\\java\\com\\main\\jars\\";

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringBeanUtil.applicationContext == null) {
            SpringBeanUtil.applicationContext = applicationContext;
        }
    }

    /**
     * applicationContext
     *
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 根据名称获取bean
     *
     * @param name
     * @return
     */
    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    /**
     * 根据class获取bean
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

    /**
     * 根据名称和class获取bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return applicationContext.getBean(name, clazz);
    }

    /**
     * 获取所有的controller
     * @param clazz
     * @return
     */
    public static String[] controllers(Class<? extends Annotation>  clazz){
        return getApplicationContext().getBeanNamesForAnnotation(clazz);
    }

    /**
     * 从本地磁盘的某个路径上加载类， 如果是class文件：
     * filePath路径应该为class文件包名的上一级，如D:\\workspace\\classes\\com\\test\\helloworld.class,那么filePath则应该是D:\\workspace\\classes
     * 如果是jar包：
     * 则是jar包所在目录，如D:\\workspace\\classes\\helloword.jar，那么filePath则应该为D:\\workspace\\classes
     */
    public static List<Class<?>> loadClass(File file, ClassLoader beanClassLoader, String jarname) {
        List<Class<?>> classList = new ArrayList<>();
//        File file = new File(filePath);
        if (file.exists() && file.isDirectory()) {
            Stack<File> stack = new Stack<>();
            stack.push(file);
            while (!stack.isEmpty()) {
                File path = stack.pop();
                // 只需要class文件或者jar包
                File[] classFiles = path.listFiles(new FileFilter() {
                    @Override
                    public boolean accept(File pathname) {
                        return pathname.isDirectory() || pathname.getName().endsWith(".class") || pathname.getName().endsWith(".jar");
                    }
                });
                for (File subFile : classFiles) {
                    if(jarname != null && jarname != "" && !jarname.equals(subFile.getName())){
                        continue;
                    }
                    if (subFile.isDirectory()) {
                        // 如果是目录，则加入栈中
                        stack.push(subFile);
                    } else {
                        URL url = null;
                        JarFile jar = null;
                        Method method = null;
                        Boolean accessible = null;
                        String className = subFile.getAbsolutePath();
                        try {
                            // 反射并调用URLClassLoader的addURL方法
                            URLClassLoader classLoader = (URLClassLoader) beanClassLoader;
                            method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
                            accessible = method.isAccessible();
                            if (accessible == false) {
                                method.setAccessible(true);
                            }
                            if (className.endsWith(".class")) {
                                // 一次性加载目录下的所有class文件 这里一定不要写成url = subFile.toURI().toURL();
                                url = file.toURI().toURL();
                                method.invoke(classLoader, url);
                                // 拼类名，并进行类型加载
                                int clazzPathLen = file.getAbsolutePath().length() + 1;
                                className = className.substring(clazzPathLen, className.length() - 6);
                                className = className.replace(File.separatorChar, '.');
                                classList.add(classLoader.loadClass(className));
                            } else if (className.endsWith(".jar")) {
                                // 如果是jar包，加载该jar包
                                url = subFile.toURI().toURL();
                                method.invoke(classLoader, url);
                                // 获取jar
                                jar = new JarFile(new File(className));
                                // 从此jar包 得到一个枚举类
                                Enumeration<JarEntry> entries = jar.entries();
                                // 同样的进行循环迭代
                                while (entries.hasMoreElements()) {
                                    // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                                    JarEntry entry = entries.nextElement();
                                    String name = entry.getName();
                                    // 如果是以/开头的
                                    if (name.charAt(0) == '/') {
                                        // 获取后面的字符串
                                        name = name.substring(1);
                                    }
                                    // 不同的系统斜杆不一样
                                    name = name.replace(File.separatorChar, '.');
                                    name = name.replace('/', '.');
                                    // 获取class文件
                                    if (name.endsWith(".class") && !entry.isDirectory()) {
                                        String className1 = name.substring(0, name.length() - 6);
                                        // 添加到classes
                                        classList.add(classLoader.loadClass(className1));
                                    }
                                }
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e.getMessage());
                        } finally {
                            if (null != jar) {
                                try {
                                    jar.close();
                                } catch (IOException e) {
                                    throw new RuntimeException(e.getMessage());
                                }
                            }
                            if (null != method && null != accessible) {
                                method.setAccessible(accessible);
                            }
                        }

                    }
                }
            }
        }
        return classList;
    }

    /**
     * 去掉Controller的Mapping
     *
     * @param controllerBeanName
     */
    public static void unregisterController(String controllerBeanName) {
        final RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
        if (requestMappingHandlerMapping != null) {
            String handler = controllerBeanName;
            Object controller = applicationContext.getBean(handler);
            if (controller == null) {
                return;
            }
            final Class<?> targetClass = controller.getClass();
            ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
                @Override
                public void doWith(Method method) {
                    Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
                    try {
                        Method createMappingMethod = RequestMappingHandlerMapping.class.
                                getDeclaredMethod("getMappingForMethod", Method.class, Class.class);
                        createMappingMethod.setAccessible(true);
                        RequestMappingInfo requestMappingInfo = (RequestMappingInfo)
                                createMappingMethod.invoke(requestMappingHandlerMapping, specificMethod, targetClass);
                        if (requestMappingInfo != null) {
                            requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, ReflectionUtils.USER_DECLARED_METHODS);
        }
    }

    /**
     * 注册Controller
     *
     * @param controllerBeanName
     * @throws Exception
     */
    public static void registerController(String controllerBeanName) throws Exception {
        final RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
        if (requestMappingHandlerMapping != null) {
            String handler = controllerBeanName;
            Object controller = applicationContext.getBean(handler);
            if (controller == null) {
                return;
            }
            unregisterController(controllerBeanName);
            //注册Controller
            Method method = requestMappingHandlerMapping.getClass().getSuperclass().getSuperclass().getDeclaredMethod("detectHandlerMethods", Object.class);
            method.setAccessible(true);
            method.invoke(requestMappingHandlerMapping, handler);
        }
    }

    /**
     * 判断类是否有某注解
     *
     * @param clazz
     * @return
     */
    public static boolean hasAnnotation(Class<?> clazz, Class annotation) {
        boolean hasController = clazz.isAnnotationPresent(annotation);
        return hasController;
    }

    /**
     * 获取项目当前目录
     * @return
     */
    public static String getProjectPath(HttpServletRequest request) throws FileNotFoundException {
        ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "application.yml");
        String path = request.getServletContext().getRealPath("/") + packageuri;
        return path;
    }
}
